package lv.rtu.ditf.dc.fs;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import lv.rtu.ditf.dc.hashmap.routines.HashMapRoutines;

/**
 * 
 * @author Pāvels Koliškins, Edgars Matisāns
 *
 */
public class FileStorage implements DocumentCategoryOperationsListener {
	private static final String BASE_DIRECTORY = "files";
	private static final String DOCUMENTS_DIRECTORY = "documents";
	private static final String INDEXES_DIRECTORY = "indexes";
	
	private static FileStorage INSTANCE = new FileStorage();
	
	private List<DocumentCategory> categories;
	private HashMap<String, Integer> documentCountByTerm;
	
	private FileStorage() {
	}
	
	public static FileStorage get() {
		return INSTANCE;
	}
	
	private static String getBaseDirectory() {
		return BASE_DIRECTORY + File.separatorChar;
	}
	
	private static String getDocumentsDirectory() {
		return getBaseDirectory() + DOCUMENTS_DIRECTORY + File.separatorChar;
	}
	
	private static String getIndexesDirectory() {
		return getBaseDirectory() + INDEXES_DIRECTORY + File.separatorChar;
	}
	
	
	/**
	 * Checks if specified directory exists and tries to create one if it doesn't.
	 * @param directory
	 * @return true, if directory already exists
	 * @throws IOException
	 */
	public static boolean checkDirectory(File directory) throws IOException {
		if (!directory.exists()) {
			if (!directory.mkdirs())
				throw new IOException("Directory no created: " + directory.getAbsolutePath());
			
			return false;
		}
		
		return true;
	}
	
	public static boolean deleteDirectoryContents(File directory) {
		File[] allFiles = directory.listFiles();
		if (allFiles == null || allFiles.length == 0)
			return true;
		
		boolean allDeleted = true;
		for (File file : allFiles) {
            if (!file.delete())
				allDeleted = false;
		}
		
		return allDeleted;
	}

    public boolean deleteDirectory(DocumentCategory documentCategory) {
        boolean directoryDelete = true;

        //delete directory contents
        File documentDirectoryBase = new File(getDocumentsDirectory() + documentCategory.getName());
        File documentDirectoryIndexes = new File(getIndexesDirectory() + documentCategory.getName());
        
        directoryDelete = directoryDelete && deleteDirectoryContents(documentDirectoryBase);
        directoryDelete = directoryDelete && deleteDirectoryContents(documentDirectoryIndexes);

        directoryDelete = directoryDelete && documentDirectoryBase.delete();
        directoryDelete = directoryDelete && documentDirectoryIndexes.delete();
        
        if (directoryDelete) {
        	if (categories != null)
        		categories.remove(documentCategory);
        	
        	if (documentCountByTerm != null)
        		HashMapRoutines.subtractAndCount(documentCountByTerm, documentCategory.getDocumentCountByTerm());
        }

        return directoryDelete;
    }
	
	
	/**
	 * Creates document category. Category name is not checked
	 * 	against illegal file names.
	 * @param categoryName category (file system directory) name
	 * @return true, if category was created.
	 * @throws IOException
	 */
	public boolean createDocumentCategory(String categoryName) throws IOException, CategoryFormatException {
        if (!categoryName.matches("^[a-zA-Z]{1,60}$")) throw new CategoryFormatException("Only latin symbols allowed!");

        File documentsDirectory = new File(getDocumentsDirectory());
        File indexesDirectory = new File(getIndexesDirectory());
		
		checkDirectory(documentsDirectory);
        checkDirectory(indexesDirectory);
		File directoryFile = new File(documentsDirectory, categoryName);
        File indexesFile = new File(indexesDirectory, categoryName);
		if (directoryFile.exists() || indexesFile.exists())
			return false;
		else {
			boolean success = directoryFile.mkdir() && indexesFile.mkdir();
			if (success && categories != null) {
				DocumentCategory category = DocumentCategory.get(directoryFile, indexesFile);
				categories.add(category);
				category.addDocumentCategoryOperationsListener(this);
			}
			
			return success;
		}
	}

	/**
	 * Load all source document categories containing documents. 
	 * @return list of all document categories
	 * @throws IOException
	 */
	public List<DocumentCategory> getDocumentCategories() throws IOException {
		if (categories == null) {
			categories = new ArrayList<DocumentCategory>();
			
			File documentsDirectory = new File(getDocumentsDirectory());
			
			// document categories parent directory doesn't exist
			if (!checkDirectory(documentsDirectory))
				return categories;
			
			// create indexes directory
			File indexesDirectory = new File(getIndexesDirectory());
			checkDirectory(indexesDirectory);
			
			File[] categoriesDirectories = documentsDirectory.listFiles();
			// document categories doesn't exist
			if (categoriesDirectories == null || categoriesDirectories.length == 0)
				return categories;
			
			// load all directories
			for (File categoryDirectory : categoriesDirectories) {
	            if (!categoryDirectory.isHidden()) {
	                DocumentCategory category = DocumentCategory.get(categoryDirectory, indexesDirectory);
	                if (category != null) {
	                    categories.add(category);
	                    category.addDocumentCategoryOperationsListener(this);
	                }
	            }
			}
		}
		
		return categories;
	}
	
	
	public Map<String, DocumentCategory> getCategoriesByNames() throws IOException {
		Map<String, DocumentCategory> categoriesByName = new HashMap<String, DocumentCategory>();
		List<DocumentCategory> documentCategories = getDocumentCategories();
		
		for (DocumentCategory category : documentCategories)
			categoriesByName.put(category.getName(), category);
		
		return categoriesByName; 
	}
	
	
	public int getTotalDocumentCount() throws IOException {
		List<DocumentCategory> documentCategories = getDocumentCategories();
		
		int totalDocumentCount = 0;
		for (DocumentCategory category : documentCategories)
			totalDocumentCount += category.getDocuments().size();
		
		return totalDocumentCount;
	}
	
	
	public HashMap<String, Integer> getDocumentCountByTerm() throws IOException {
		if (documentCountByTerm == null) {
			List<DocumentCategory> documentCategories = getDocumentCategories();
			
			documentCountByTerm = new HashMap<String, Integer>();
			for (DocumentCategory category : documentCategories)
				HashMapRoutines.mergeAndSum(documentCountByTerm, category.getDocumentCountByTerm());
		}
		
		return documentCountByTerm;
	}

    /**
	 * Load some specific document category.
	 * @return list of all document categories
	 * @throws IOException
	 */
	public DocumentCategory getDocumentCategory(String categoryName) throws IOException {
		if (categories != null)
			for (DocumentCategory category : categories)
				if (categoryName.equals(category.getName()))
					return category;
		
		File documentsDirectory = new File(getDocumentsDirectory());
		File indexesDirectory = new File(getIndexesDirectory());

		// document categories parent directory doesn't exist
		if (!checkDirectory(documentsDirectory))
			return null;
		
		// create indexes directory		
		checkDirectory(indexesDirectory);

        File documentCategoryFile = new File (documentsDirectory, categoryName);
        DocumentCategory category = DocumentCategory.get(documentCategoryFile, indexesDirectory);
        if (categories != null && category != null) {
        	categories.add(category);
        	category.addDocumentCategoryOperationsListener(this);
        }
        
        return category;
	}
	
	public void buildIndexes() throws IOException {
		File indexesDirectory = new File(getIndexesDirectory());
		
		// indexes categories parent directory doesn't exist
		checkDirectory(indexesDirectory);
		
		// delete old indexes categories
		if (!deleteDirectoryContents(indexesDirectory))
			throw new IOException("Unable to delete indexes directories");
		
		// build indexes
		List<DocumentCategory> documentCategories = getDocumentCategories();
		for (DocumentCategory category : documentCategories)
			category.buildIndexes();
	}
	
	
	public static void main(String[] args) {
		FileStorage storage = new FileStorage();
		try {
			List<DocumentCategory> categories = storage.getDocumentCategories();
			if (categories != null)
				for (DocumentCategory category : categories) {
					System.out.println("Category: " + category.getName());
					for (Document document : category.getDocuments()) {
						System.out.println("Document :" + document.getName());
						System.out.println("Content :");
						System.out.println(document.getPlainTextContent());
					}
				}
		} catch (IOException e) {
			// TODO log
			e.printStackTrace();
		}
	}

	@Override
	public void onDocumentAdded(DocumentCategoryOperationEvent event) {
		if (documentCountByTerm != null) {
			HashMap<String, Integer> newFrequencies = event.getDocument().getPrecalculatedTermFrequencies();
			
			HashMapRoutines.mergeAndCount(documentCountByTerm, newFrequencies);	
		}
	}
}
